我平常主要使用 YouTube Music 聽歌,也滿常看 YouTube Music 提供的季度 Recap 和年度 Recap。
它會告訴我這段時間最常聽哪些歌曲、最常聽哪些歌手,以及一些個人化的音樂統計。
但我開始想到一個問題:
如果我的音樂紀錄散落在不同平台呢?
假設今天同時使用 YouTube Music、Spotify,甚至 Apple Music,那每個平台只會看到自己平台內的紀錄。
Spotify 不知道我在 YouTube Music 聽了什麼,YouTube Music 也不知道我在 Spotify 聽了什麼。
這樣算出來的年度回顧,其實都只是其中一部分。
所以這次參加 iThome 鐵人賽,我想試著做一件事:
使用 ChatGPT 與 Codex,打造一套可以整合不同音樂平台聆聽紀錄的 Music Recap。
除了常見的 Top Songs、Top Artists 和聆聽時間之外,我也希望最後可以做出一些官方 Recap 沒有提供的統計。
例如:
如果最後真的能把不同平台的紀錄放在一起,應該就可以做出一份真正屬於自己的 Music Recap。
這其實是我最擔心的事情。
想法再漂亮,如果平台根本不讓使用者取得自己的播放紀錄,那這個專案做到最後可能什麼都沒有。
所以 Day 1 第一件事,就是先確認各平台到底能取得哪些資料。
目前我先研究三個平台:
Spotify 提供個人資料匯出功能,其中 Extended Streaming History 可以取得相當完整的串流紀錄。
官方文件列出的資料包含:
其中最重要的是「實際播放毫秒數」。
因為有這個欄位,就可以知道某次播放到底聽了多久,而不是只知道「曾經播放過這首歌」。
所以 Spotify 目前看起來會是這個專案最穩定的資料來源之一。
Google 可以透過 Google Takeout 匯出 YouTube 與 YouTube Music 的相關資料。
問題是 YouTube Music 和一般 YouTube 本身的歷史紀錄存在一定程度的關聯。
所以目前還不能直接假設:
Google Takeout 匯出的每一筆 YouTube 歷史都是 YouTube Music 的歌曲播放紀錄。
實際拿到資料之後,還需要研究:
因為我自己主要使用 YouTube Music,所以接下來會優先研究這個平台。
Apple 也提供使用者申請個人資料副本,其中包含 Apple Music 的服務使用資訊。
不過目前還沒有實際取得匯出檔,所以我暫時不會假設 Apple Music 一定可以取得和 Spotify 一樣完整的資料。
因此第一版系統會先以:
YouTube Music + Spotify
作為主要目標。
Apple Music 則會先保留擴充的位置,等真正拿到資料之後再決定支援程度。
這樣就算 Apple Music 最後能取得的欄位比較少,也不會把整個專案卡住。
不同平台最大的問題之一,就是資料格式一定不一樣。
Spotify 可能有:
trackName
artistName
albumName
msPlayed
spotify_track_uri
YouTube Music 可能提供的欄位又完全不同。
如果後面的統計程式每次都要知道資料來自哪個平台,整個系統很快就會變得很難維護。
所以我打算先把不同平台的資料轉成同一種格式。
暫時把一筆播放紀錄稱為:
ListeningEvent
例如:
{
"event_id": "demo-005",
"source": "youtube_music",
"occurred_at": "2026-09-01T10:25:00+08:00",
"timestamp_semantics": "source_event_time",
"source_track_id": "demo-yt-night",
"raw_title": "夜色",
"raw_artist": "示範樂團",
"played_ms": null,
"duration_kind": "unknown"
}
目前最重要的欄位大概有:
event_id
source
occurred_at
source_track_id
raw_title
raw_artist
played_ms
平台原本提供的歌曲名稱和歌手名稱,也會先完整保留下來。
因為跨平台歌曲名稱的整理,會是之後另外一個大問題。
在設計資料格式的時候,很快就碰到第一個很容易算錯的地方。
假設我知道:
某首歌長度是 4 分鐘。
播放紀錄也顯示:
我曾經播放過這首歌。
那可以直接認定我聽了 4 分鐘嗎?
不行。
因為我可能:
所以:
歌曲長度不能直接當成實際收聽時間。
如果來源沒有提供實際播放時長,我會把:
"played_ms": null
保留下來。
null 的意思是:
不知道。
而不是 0。
這兩個狀態必須分開。
例如:
"played_ms": 0
代表來源真的告訴我們這次播放時間是 0。
但:
"played_ms": null
則代表來源根本沒有這項資訊。
如果一開始沒有把這兩種情況分開,之後計算總聆聽時間就很容易產生錯誤。
另外一個要先定義清楚的問題是:
如果歷史紀錄裡面有一筆:
YOASOBI - アイドル
我們只能確定:
系統記錄到一次相關播放事件。
不能直接說:
我完整聽完了一次《アイドル》。
所以未來在文章和系統裡,我會區分:
活動紀錄數
以及:
完整播放次數
除非資料真的能證明歌曲有完整播放,否則不會把兩者混在一起。
今天先沒有急著處理真正的 Spotify 或 YouTube Music 匯出檔。
我先建立了一份假的測試資料。
例如:
[
{
"event_id": "demo-001",
"source": "spotify",
"raw_title": "晨光",
"raw_artist": "示範歌手",
"played_ms": 180000
},
{
"event_id": "demo-002",
"source": "youtube_music",
"raw_title": "夜色",
"raw_artist": "示範樂團",
"played_ms": null
}
]
接著先寫一個很小的 Python 程式,讓它可以:
這份測試資料總共有 8 筆紀錄。
結果如下:
| 項目 | 結果 |
|---|---|
| 活動紀錄數 | 8 |
| 有時長的紀錄 | 4 |
| 未知時長的紀錄 | 4 |
| 明確記錄為 0 秒 | 1 |
| 已知時長加總 | 8 分鐘 |
| 時長欄位覆蓋率 | 50% |
這裡有一個很重要的地方。
「8 分鐘」只能代表:
目前資料中,可以確定知道的播放時間加起來是 8 分鐘。
不能說:
總共聽了 8 分鐘。
因為還有 4 筆紀錄根本不知道播放多久。
同樣的:
時長欄位覆蓋率 50%
也只是代表:
8 筆測試紀錄裡有 4 筆提供播放時間。
不是代表目前已經取得完整音樂歷史的一半。
之後匯入音樂紀錄,很可能遇到同一份資料被匯入兩次。
例如第一次匯入:
event-123
第二次又匯入:
event-123
如果內容完全相同,就不應該重複計算。
所以目前先使用:
source + event_id
作為其中一種重複判斷方式。
如果 ID 一樣,而且內容也一樣,就視為重複紀錄。
但如果 ID 一樣,內容卻不一樣,就不能偷偷選其中一筆。
系統應該直接回報:
Conflict
讓問題被發現。
目前 Day 1 還沒有處理這件事。
但我已經可以預見,它可能會是整個專案最有趣的技術問題之一。
例如:
YOASOBI - アイドル
在另一個平台可能變成:
YOASOBI「アイドル」Official Music Video
甚至可能還有:
Idol
アイドル (Live)
アイドル - English Version
アイドル (Remastered)
哪些是同一首歌?
哪些應該分開統計?
單純把名稱轉成小寫再比對一定不夠。
所以目前的原則是:
先保留平台原始資料,不急著自動合併。
歌曲 Identity Matching 會在之後的文章另外處理。
既然這次參加的是 ChatGPT & Codex 主題競賽,我也希望這個系列不是單純叫 AI 幫我生程式碼。
目前預計的分工是:
主要負責:
主要負責:
另外我也建立了一份 AGENTS.md。
這個檔案會放一些 Codex 在這個專案裡必須遵守的規則。
例如:
未知時長不能自行補成歌曲完整長度。
不同平台歌曲名稱相似,不代表一定是同一首歌。
測試資料必須明確標示為 Demo Data。
不能為了讓統計結果完整,而自行猜測缺少的資料。
希望後面專案越來越大時,AI 不會做到一半突然忘記前面的規則。
一開始我也想到:
能不能算出我是某位歌手全球前 0.01% 的聽眾?
YouTube Music 有時候會提供類似的徽章。
這看起來很適合放在自己的 Recap。
但是仔細想就會發現:
如果我只有自己的播放紀錄,我根本不知道其他人的分布。
沒有所有聽眾的資料,就無法真的知道自己位於:
Top 10%
Top 1%
Top 0.1%
Top 0.01%
所以這個功能目前先排除。
未來可以設計自己的:
忠誠度
單曲循環程度
探索率
回鍋率
但這些都會使用我們自己公開定義的演算法。
不會假裝它是官方的全球排名。
今天沒有急著做漂亮的 Recap 頁面。
目前先完成的是整個專案最底層的基礎:
目前使用的還全部都是測試資料。
真正的跨平台 Music Recap,還沒有開始。
但至少已經確定:
這個題目真的有資料可以做,而且最基本的架構是走得通的。
接下來最重要的,就是把假資料換成真正的音樂紀錄。
下一篇會先從我最常使用的 YouTube Music 開始。
我會實際從 Google Takeout 匯出資料,看看拿到的檔案到底長什麼樣子。
接著確認:
下一篇開始,就要正式碰真實資料了。